/* bzflag
 * Copyright (c) 1993-2020 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "LinuxDisplay.h"
#include "XWindow.h"

#if defined(USE_XF86VIDMODE_EXT)
#include <stdio.h>

// evaluates to the (approximate) vertical retrace speed of modeinfo _r
#define getRetrace(_r)  ((int)(0.5f + (1000.0f * (_r)->dotclock) / \
                    ((_r)->htotal * (_r)->vtotal)))

static int      resolutionCompare(const void* _a, const void* _b)
{
    const XF86VidModeModeInfo* a = *((const XF86VidModeModeInfo* const *)_a);
    const XF86VidModeModeInfo* b = *((const XF86VidModeModeInfo* const *)_b);

    // test the stuff we actually care about
    if (a->hdisplay < b->hdisplay) return -1;
    if (a->hdisplay > b->hdisplay) return 1;
    if (a->vdisplay < b->vdisplay) return -1;
    if (a->vdisplay > b->vdisplay) return 1;
    if (getRetrace(a) < getRetrace(b)) return -1;
    if (getRetrace(a) > getRetrace(b)) return 1;

    // other info can be ordered arbitrarily
    return 0;
}

//
// LinuxDisplayMode
//

LinuxDisplayMode::LinuxDisplayMode() : display(NULL),
    numResolutions(0),
    lastResolution(-1),
    resolutions(NULL),
    origNumResolutions(0),
    origResolutions(NULL)
{
    // do nothing
}

LinuxDisplayMode::~LinuxDisplayMode()
{
    delete[] resolutions;
    if (origResolutions)
        XFree(origResolutions);
}

XDisplayMode::ResInfo** LinuxDisplayMode::init(XDisplay* _display,
        int& numModes, int& currentMode)
{
    int i, eventbase, errorbase;

    // save display for later
    display = _display;

    // Check if we have the XF86 vidmode extension
    if (!XF86VidModeQueryExtension(display->getRep()->getDisplay(),
                                   &eventbase, &errorbase))
        return NULL;

    // get available resolutions
    if (!XF86VidModeGetAllModeLines(display->getRep()->getDisplay(),
                                    display->getRep()->getScreen(),
                                    &numResolutions, &origResolutions))
        return NULL;

    // get current resolution
    int dotclock;
    XF86VidModeModeLine mode;
    XF86VidModeGetModeLine(display->getRep()->getDisplay(),
                           display->getRep()->getScreen(),
                           &dotclock, &mode);

    // make a copy of the original data
    origNumResolutions = numResolutions;
    resolutions = new XF86VidModeModeInfo*[numResolutions];
    for (i = 0; i < numResolutions; i++)
        resolutions[i] = origResolutions[i];

    // sort resolutions
    qsort(resolutions, numResolutions, sizeof(resolutions[0]), resolutionCompare);

    // find current resolution in the available resolution list
    int current;
    for (current = 0; current < numResolutions; current++)
    {
        const XF86VidModeModeInfo* r = resolutions[current];
        if (dotclock    == (int)r->dotclock &&
                mode.hdisplay   == r->hdisplay &&
                mode.hsyncstart == r->hsyncstart &&
                mode.hsyncend   == r->hsyncend &&
                mode.htotal     == r->htotal &&
                mode.vdisplay   == r->vdisplay &&
                mode.vsyncstart == r->vsyncstart &&
                mode.vsyncend   == r->vsyncend &&
                mode.vtotal     == r->vtotal &&
                mode.flags      == r->flags)
            break;
    }

    // no switching if current mode not found
    if (current == numResolutions)
        return NULL;

    // compress out modes that are (effectively) duplicates.  never
    // remove the current mode.
    for (i = 0; i < numResolutions - 1; )
    {
        if (resolutionCompare(resolutions + i, resolutions + i + 1) == 0)
        {
            // is next resolution the current mode?  if so then move it down
            // so we don't blow it away.
            if (current == i + 1)
                resolutions[i] = resolutions[i + 1];

            // move remaining resolutions down (overwriting the one after i)
            for (int j = i + 2; j < numResolutions; j++)
                resolutions[j - 1] = resolutions[j];

            // do we move the current resolution down too?
            if (current > i)
                current--;

            // now one less resolution
            numResolutions--;
        }
        else
            i++;
    }

    // make ResInfo list
    char name[80];
    ResInfo** resInfo = new ResInfo*[numResolutions];
    for (i = 0; i < numResolutions; i++)
    {
        const XF86VidModeModeInfo* r = resolutions[i];
        sprintf(name, "%dx%d @%d", r->hdisplay, r->vdisplay, getRetrace(r));
        resInfo[i] = new ResInfo(name, r->hdisplay, r->vdisplay, getRetrace(r));
    }

    // return modes
    numModes = numResolutions;
    currentMode = current;
    lastResolution = current;
    return resInfo;
}

bool            LinuxDisplayMode::set(int index)
{
    return doSet(index, true);
}

bool            LinuxDisplayMode::setDefault(int index)
{
    return doSet(index, false);
}

bool            LinuxDisplayMode::doSet(int index, bool position)
{
    // ignore attempts to set video format to current format.
    // normally this only happens when restoring the default
    // format, when BzfDisplay deliberately forces the change.
    // that's useful for win32 where the OS knows the right
    // format and will ignore calls to switch the current
    // format.  however, irix isn't so clever and may cause
    // the display to flicker even when the format isn't
    // really changing.
    if (index == lastResolution || numResolutions <= 1)
        return true;

    // deactivate windows before resolution change.  if we don't do this
    // then the app will almost certainly crash in the OpenGL driver.
    XWindow::deactivateAll();

    // change resolution
    if (XF86VidModeSwitchToMode(display->getRep()->getDisplay(),
                                display->getRep()->getScreen(),
                                resolutions[index]))
    {
        if (position)
        {
            // kludge for accelerated GLX.  when we set the view port after
            // changing the resolution just before quiting, GLX does not
            // release the display to X server control.  or something like
            // that.  the effect is that you see the game window still on
            // the screen but maybe shifted around and you can't see any of
            // the other windows.  without this code, a workaround for the
            // problem is ctrl_alt_+ or ctrl_alt_- to force a resize.
            XF86VidModeSetViewPort(display->getRep()->getDisplay(),
                                   display->getRep()->getScreen(), 0, 0);
        }
        XSync(display->getRep()->getDisplay(), false);
        lastResolution = index;

        // reactivate previously deactivated window after change
        XWindow::reactivateAll();
        return true;
    }

    // reactivate previously deactivated window after change
    XWindow::reactivateAll();
    return false;
}

#endif

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
